package com.matrix.geo.playmysqlgeo.config;

import com.matrix.geo.playmysqlgeo.typehandler.MySQLGeometryTypeHandler;
import com.matrix.geo.playmysqlgeo.typehandler.OracleGeometryTypeHandler;
import com.matrix.geo.playmysqlgeo.typehandler.SQLServerGeometryTypeHandler;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandler;

import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

@Intercepts(@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}))
public class DynamicTypeHandlerInterceptor implements Interceptor {
    private static final String MYSQL_JDBC_URL_PREFIX = "jdbc:mysql";
    private static final String SQL_SERVER_JDBC_URL_PREFIX = "jdbc:sqlserver";

    private static final String ORACLE_JDBC_URL_PREFIX = "jdbc:oracle";
    private static final String JTS_TYPE_HANDLER_PACKAGE = "com.matrix.geo.playmysqlgeo.typehandler";

    private static final MySQLGeometryTypeHandler MY_SQL_GEOMETRY_TYPE_HANDLER = new MySQLGeometryTypeHandler();
    private static final SQLServerGeometryTypeHandler SQL_SERVER_GEOMETRY_TYPE_HANDLER = new SQLServerGeometryTypeHandler();
    private static final OracleGeometryTypeHandler ORACLE_GEOMETRY_TYPE_HANDLER = new OracleGeometryTypeHandler();

    /**
     * TypeHandler必须放至com.matrix.geo.playmysqlgeo.typehandler包下, 且必须如此命名: ${databaseType}.Geometry,
     * 例如: SQLServer则为: SQLServerGeometryTypeHandler
     * 例如: MySQL则为: MySQLGeometryTypeHandler
     */
    private final Map<String, TypeHandler> typeHandlers = new HashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        String jdbcUrl = getJdbcUrl(mappedStatement.getConfiguration().getEnvironment().getDataSource());
        String databaseType = getDatabaseType(jdbcUrl);

        // 根据数据库类型选择TypeHandler
        TypeHandler typeHandler = switch (databaseType) {
            case "MySQL" -> MY_SQL_GEOMETRY_TYPE_HANDLER;
            case "SQLServer" -> SQL_SERVER_GEOMETRY_TYPE_HANDLER;
            case "Oracle" -> ORACLE_GEOMETRY_TYPE_HANDLER;
            default -> throw new IllegalStateException("Unexpected value: " + databaseType);
        };

        // 使用选择的TypeHandler处理参数
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        Object parameterObject = parameterHandler.getParameterObject();
        if (parameterObject != null && typeHandler != null) {
            MetaObject parameterMetaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
            String parameterName = (String) metaObject.getValue("delegate.boundSql.parameterMappings[0].property");
            metaObject.setValue("delegate.boundSql.parameterMappings[0].typeHandler", typeHandler);
            metaObject.setValue("delegate.boundSql.hasTypeHandlerFor(parameterObject.getClass())", true);
        }

        return invocation.proceed();
    }

    private String getJdbcUrl(DataSource dataSource) {
        try {
            Method method = dataSource.getClass().getMethod("getJdbcUrl");
            return (String) method.invoke(dataSource);
        } catch (Exception e) {
            throw new RuntimeException("Error getting JDBC url from DataSource", e);
        }
    }

    private String getDatabaseType(String jdbcUrl) {
        if (jdbcUrl.startsWith(MYSQL_JDBC_URL_PREFIX)) {
            return "MySQL";
        } else if (jdbcUrl.startsWith(SQL_SERVER_JDBC_URL_PREFIX)) {
            return "SQLServer";
        } else if (jdbcUrl.startsWith(ORACLE_JDBC_URL_PREFIX)) {
            return "Oracle";
        } else {
            throw new RuntimeException("Unknown database type for JDBC url: " + jdbcUrl);
        }
    }

}
